<?php
/**
 *  @desc 大文件处理类
 *  @author 陈聪
 */
namespace Org\Util;
class BigFile {
    // 文件目录
    private $root = '/';

    // 文件后缀
    private $suffix = '.log';

    // 文件句柄
    private $handle = null;

    // 一次读取文件的最大记录数
    private $limit = 40000;

    // 每行读取的字节长度
    private $length = 8192;

    // 开始时间
    private $startTime = 0;

    // 内存使用基准点
    private static $startMemory = 0;

    private $conn = null;

    private static $init = null;
    public static function instance() {
        self::$startMemory = memory_get_usage ( true );
        if (self::$init && is_object ( self::$init )) {
            return self::$init;
        }
        self::$init = new self ();
        return self::$init;
    }

    public function __construct() {
    }

    /**
     * @desc 设置目录
     * @param string $root
     * @return boolean
     */
    public function setRoot($root) {
        if (! is_dir ( $root )){
            return false;
            //die ( $root . ' ROOT DOES NOT EXIST' );
        }
        $this->root = $root;
        return true;
    }

    /**
     * @desc 设置后缀名
     * @param string $suffix
     */
    public function setSuffix($suffix) {
        $this->suffix = $suffix;
    }

    /**
     * @desc 设置PHP内存
     * @param unknown $limit
     */
    public function setLimit($limit) {
        if (! is_numeric ( $limit )){
            //die ( $limit . ' SHOULD BE NUMBERIC' );
            return false;
        }
        if (intval ( $limit ) > 1000000){
            //die ( $limit . ' SHOULD BE LOWER THAN 1000000' );
            return false;
        }
        $this->limit = intval ( $limit );
        return true;
    }

    /**
     * @desc 判断文件是否存在并且可读
     * @param string $date
     * @param string $appid
     * @param string $op
     * @return string
     */
    public function _getFile($filename) {
        $filename = rtrim ( $this->root, '/' ) . DIRECTORY_SEPARATOR . $filename . $this->suffix;
        if (! file_exists ( $filename )) {
            //die ( $filename . ' FILE DOES NOT EXISTS!' );
            return false;
        }
        if (! is_file ( $filename )) {
            //die ( $filename . ' FILE DOES NOT EXIST!' );
            return false;
        }

        if (! is_readable ( $filename )) {
            //die ( $filename . '  FILE ACCESS DENY!' );
            return false;
        }
        return $filename;
    }

    /**
     * @desc 关闭文件
     * @param string $date
     * @param string $appid
     * @param string $op
     * @return boolean
     */
    public function closeFile($filename) {
        if ($filename) {
            if (is_object ( $this->handle [$filename] ) || $this->conn [$filename]) {
                unset ( $this->handle [$filename] );
                $this->handle [$filename] = null;
            }
            $this->conn [$filename] = null;
            $this->handle [$filename] = null;
            unset ( $this->handle [$filename] );
        } else {
            if (is_array ( $this->handle ) && $this->handle) {
                foreach ( $this->handle as $key => $val ) {
                    unset ( $this->handle [$key] );
                    $this->conn [$key] = null;
                    $this->handle [$key] = null;
                }
            }
        }
        return true;
    }

    /**
     * @desc 打开文件
     * @param string $date
     * @param string $appid
     * @param string $op
     */
    private function _openFile($filename) {
        $this->startTime = microtime ( true );
        if (isset ( $this->conn [$filename] ) && $this->conn [$filename]) {
            return $this->handle [$filename];
        }

        $filename2 = self::_getFile ( $filename );
        if ($filename2 && ($this->handle [$filename] = new \SplFileObject ( $filename2, 'r' )) != null) {
            $this->conn [$filename] = true;
            return $this->handle [$filename];
        } else {
            //die ( 'FILE OPEN FAILED!' );
            return false;
        }
    }

    /**
     * 功能：解析数据
     * 格式： array('timestamp','mid','data');
     * @param string $data
     * @return boolean array
     */
    private function _parseData($data) {
        if (empty ( $data ) || ! is_string ( $data )){
            return false;
        }
        return @json_decode ( $data, true );
    }

    /**
     * TODO:读取单一文件
     * @param string $filename：
     * @param number $startNum 默认从第一行开始
     * @param number $length 默认到$this->limit 读取的行数
     * @param array $condition:array('mid'=>arrray()
     *            , 'ip'=>array() , ...) 过滤条件
     * @return array(count , diffTime , memory , data)
     */
    public function readFile($filename, $startNum = 0, $length = 0, $condition = array()) {
        $data=array();
        $data['rs'] = array();
        $data ['count'] = 0;
        $startNum = intval ( $startNum );
        $startNum = $startNum<0 ? 0 : $startNum;
        $length = intval( $length ) ;
        $length = $length<0? $this->limit : $length;

        $handle = self::_openFile ( $filename );
        $line_number = 0;

        if ($handle) {
            $handle->seek ( $startNum );
            $handle->setMaxLineLen ( $this->length );
            while ($line_number-$startNum < $length-1 ) {
                $tmp = $handle->current ();
                if (empty ( $tmp ) || trim($tmp)=="")
                    continue;
                $tmp = self::_parseData ( $tmp );
                $line_number = $handle->key ();
                $line_number=intval ( $line_number );
                if (isset ( $condition ) && $condition) {
                    $isTrue=1;
                    foreach($condition as $key=>$val){
                        if(!isset ( $tmp[$key]) || !stristr($tmp[$key],$val)){
                            $isTrue=0;
                        }
                    }
                    if ($isTrue) {
                        $data ['count'] ++;
                    }
                } else {
                    $data ['rs'] [$line_number] = $tmp;
                    $data ['count'] ++;
                }
                if ($line_number-$startNum>=$length-1){
                    break;
                }
                unset ( $tmp );
                $handle->next ();
            }
            self::closeFile ( $filename );
            unset ( $tmp, $length, $line_number, $condition );
        }
        $data ['diffTime'] = doubleval ( microtime ( true ) ) - doubleval ( $this->startTime );
        $data ['memory'] = doubleval ( (doubleval ( memory_get_usage ( true ) ) - doubleval ( self::$startMemory )) / 1024 / 1024 ) . ' M';
        return $data;
    }

    /**
     * @desc 需要countFile方法配合使用,可以倒序搜索
     * @param string $filename：
     * @param number $line_numbers 获取指定行的数据
     */
    public function readFileByLines($filename,$startNum, $length, $line_numbers,$error=true) {
        $data=array();
        $data['rs'] = array();
        $handle = self::_openFile ( $filename );
        if ($handle) {
            $handle->setMaxLineLen ( $this->length );
            foreach($line_numbers AS $key=>$line_number){
                if($key<$startNum || $key>=$startNum+$length){
                    continue;
                }
                $handle->seek ( $line_number );
                $tmp = $handle->current ();
                if (empty ( $tmp ) || trim($tmp)==""){
                    $data['rs'][$line_number]=array();
                    continue;
                }
                $tmp = $error?self::_parseData ( $tmp ):$tmp;
                $data ['rs'] [$line_number] = $tmp;
            }
            self::closeFile ( $filename );
            unset ( $tmp,$filename, $line_numbers, $line_number, $condition );
        }
        $data ['diffTime'] = doubleval ( microtime ( true ) ) - doubleval ( $this->startTime );
        $data ['memory'] = doubleval ( (doubleval ( memory_get_usage ( true ) ) - doubleval ( self::$startMemory )) / 1024 / 1024 ) . ' M';
        return $data;
    }

    /**
     * TODO:命令行下获取文件总记录数*
     *
     * @param string $filename
     * @return array
     */
    public function total_lineFile($filename) {
        $this->_openFile ( $filename );
        $file = escapeshellarg ( $this->_getFile ( $filename ) ); // 对命令行参数进行安全转义
        $line = `wc -l  $file`;
        if (preg_match ( "/(\d{1,})/", $line, $ret )) {
            $data ['count'] = $ret [1];
        } else {
            $data ['count'] = 0;
        }
        $data ['diffTime'] = doubleval ( microtime ( true ) ) - doubleval ( $this->startTime );
        $data ['memory'] = doubleval ( (doubleval ( memory_get_usage ( true ) ) - doubleval ( self::$startMemory )) / 1024 / 1024 ) . ' M';
        return $data;
    }

    /**
     * TODO:统计$filename.log记录数
     *
     * @param string $filename
     * @param array $condition
     * @return array
     */
    public function countFile($filename, $condition = array()) {
        $data=array();
        $data ['count'] = 0;
        $handle = self::_openFile ( $filename );
        if ($handle) {
            $handle->setMaxLineLen ( $this->length );
            while ( ! $handle->eof () ) {
                $tmp = $handle->current ();
                $line_number = $handle->key ();
                $line_number=intval ( $line_number );
                if (empty ( $tmp ) || trim($tmp)=="")
                    continue;
                if ($condition && is_array ( $condition )) {
                    $tmp = self::_parseData ( $tmp );
                    $isTrue=1;
                    foreach($condition as $key=>$val){
                        if(is_array($val)){
                            $isTrue2=0;
                            foreach($val AS $k=>$v){
                                if(isset ( $tmp[$k]) && stristr($tmp[$k],$v)){
                                    $isTrue2=1;
                                }
                            }
                            if($isTrue==1&&$isTrue2==0){
                                $isTrue=0;
                            }
                        }else{
                            if(!isset ( $tmp[$key]) || !stristr($tmp[$key],$val)){
                                $isTrue=0;
                            }
                        }
                    }
                    if ($isTrue) {
                        $data ['line_numbers'][$data ['count']]=$line_number;
                        $data ['count'] ++;
                    }
                } else {
                    $data ['line_numbers'][$data ['count']]=$line_number;
                    $data ['count'] ++;
                }

                unset ( $tmp );
                $handle->next ();
            }
        }
        unset ( $handle, $condition, $tmp, $key, $val );
        self::closeFile ( $filename );

        $data ['diffTime'] = doubleval ( microtime ( true ) ) - doubleval ( $this->startTime );
        $data ['memory'] = doubleval ( (doubleval ( memory_get_usage ( true ) ) - doubleval ( self::$startMemory )) / 1024 / 1024 ) . ' M';
        return $data;
    }
}
?>
